package server;
import java.io.*;
import java.net.*;
import java.util.Iterator;
import java.util.Vector;

/**
 * @author dinganw
 *
 */
class serverThread implements Runnable {   
	//socket parameters
	private Socket server;
	final static int BUF_LEN= 4096; 
	final static int TIMEOUT = 20000;
	
	static Vector<TXN> txntable=new Vector();
	static int i=-1;//to counting next generating transaction number
	
	//socket variables
	private BufferedReader socketIn=null;
	private DataOutputStream socketOut = null;
	
	//read streamfiles variables
	File f=null;
	private DataInputStream fileIn = null;
	
	//to store and process request messageHead and data
	private String messageHead = "";
	private String data="";		
	private static String filePath;
	
	String[] result;
	private String method="", txn_id = "-1", seq_num = "0";
	private String content_length = "0";
	
	final static int EXP_FIELDS = 3;
	
	//the response message variables
	private String reasonContent_length = "";
	private String reason="";	
	private String responseMessage;

	
    serverThread(Socket server)
    {
      this.server=server;
    }
     
    public String getFilePath(){
    	return filePath;
    } 
    
    public static void setFilePath(String _filePath){
    	filePath=_filePath;
    }
    
    public void run ()
    {
	   try{	
	    socketIn = new BufferedReader (new InputStreamReader(new BufferedInputStream(server.getInputStream())));
	    socketOut = new DataOutputStream(server.getOutputStream());	    
//		server.setSoTimeout(TIMEOUT);

	    messageHead=socketIn.readLine()+socketIn.readLine();				
		data=socketIn.readLine();
		
		//parseClientCommand and do corresponding action		
		if(messageHead.length()==0){//wrong format
			reason="Received a blank message ~_~";
			System.out.println(reason);
			responseMessage=errorMessageReady(204);
			sendToClient(responseMessage);	
		}else{
		processClientCommand();	
		}//end if else
		
	   }catch(SocketException se){
			if(se.getMessage().equals("Connection reset"))
				return;
			else{
				System.out.println("Could not read from or write to the socket: " + se.getMessage());
				return;
			}
		}catch(SocketTimeoutException ste){
			System.out.println("Timed out after "+TIMEOUT+"ms. No more bytes are coming from the Client.");
			return ;
		}catch( IOException ioe){
			System.out.println("Could not read buffer: " + ioe.getMessage());
			return;
		}	
    }
    
    private void processClientCommand(){ 	 	
		result = messageHead.split("\\s+");		
		// to check the message format
		if(result.length < EXP_FIELDS)
		{	
			reason="Wrong format, message head should have 4 fields!";
			System.out.println(reason);
			responseMessage=errorMessageReady(204);
			sendToClient(responseMessage);
			return ;
		}	
		
		System.out.println("=======================");
		System.out.println("====>RECEIVED from Client:");
		System.out.println(messageHead);
		System.out.println(data);
		System.out.println("====> end message");
		
			method = result[0];
			txn_id = result[1];
			seq_num = result[2];		
			content_length = result[3];	
		
		if(isMessageHeadFormatCorrect()==false){
			responseMessage=errorMessageReady(204);
			sendToClient(responseMessage);
			return ;
		}
	
		try {
			if(method.equals("READ"))		read();
			if(method.equals("NEW_TXN")) 	newTXN();
			if(method.equals("WRITE"))  	write();
			if(method.equals("COMMIT"))  	commit();
			if(method.equals("ABORT"))  	abort();
		} catch (IOException e) {
			responseMessage=errorMessageReady(205);
			sendToClient(responseMessage);
			return;
		}
		
		return ;   	
    }//end processClientCommand
  
    private void read() throws IOException{	 	
    	String fileName=data;
    	String fileRead;
    	byte[] buffer = new byte[BUF_LEN-1];
		// to check whether the file path is correct
		if(isFilePathCorrect()!=true){
			responseMessage=errorMessageReady(204);
			sendToClient(responseMessage);
			return ;	
		}// end if
		try{
		f= new File(filePath,fileName);
		FileInputStream fr= new FileInputStream(f);
		fileIn = new DataInputStream(fr);
		}catch(FileNotFoundException err){
			responseMessage=errorMessageReady(206);
			sendToClient(responseMessage);
			return;
		}
		fileIn.read(buffer);
		fileRead=new String(buffer);
		
		responseMessage=fileRead;
		sendToClient(responseMessage);
		return ;	
	 }
   
 	private void newTXN()throws IOException{
 		String fileName=data;
 		method="ACK";
 		txn_id=new Integer(++i).toString(); //generate this id for new transaction
 		TXN txn = new TXN(i,messageHead,filePath,fileName);	//record transaction
		txntable.add(txn);// keep transaction in the vector		
 		sendResponseMessage();
    } 
   
    private void write(){  
    	int txnID =(new Integer(txn_id)).intValue();
    	TXN writeTXN = findTXN(txnID);
    	
    	//to check errors of 201 and 202
    	if(writeTXN==null){
    		sendToClient(errorMessageReady(201));
    		return ;
    	}
    	if(writeTXN.receivedSequence.contains(seq_num)){
    		sendToClient(errorMessageReady(202));
    		return ;
    	}
    	
    	writeTXN.addSequence(seq_num);
    	++writeTXN.totalNumber;
    	writeTXN.addData(data);
    	
	    if(writeTXN.totalNumber%3==0){
	    	method="ACK";	
	    	sendResponseMessage();
    	}//end if
    	
    	return ;
    }
    
    private void commit() throws IOException{
    	int txnID = (new Integer(txn_id)).intValue();
    	TXN commitTXN = findTXN(txnID);
    	
    	//to check errors of 201 and 202
    	if(commitTXN==null){
    		sendToClient(errorMessageReady(201));
    		return ;
    	}	
    	//successfully commit
    	int totalNum=(new Integer(seq_num)).intValue();
    	
    	//partial commit -to be implemented
    	if(commitTXN.totalNumber>totalNum){
    		sendToClient("partial commit!!!");	
    	}else if(commitTXN.totalNumber==totalNum)
    	{//correct commit
    			commitTXN.commit();
    			method="ACK";
    			return ;
    	}else{
    	//need to ask client for resending
    		method="ASK_RESEND";
    		String resendSeq_num=commitTXN.findResendSeq(totalNum);
    		seq_num=resendSeq_num;
    	}
    	//response to client
    	sendResponseMessage();   	
    }
    
    private void abort(){

    }
    
    /**
     * @param txnID
     * @return -find it :the transaction object which containing this ID
     * 		   -no find it :
     */
    private TXN findTXN(int txnID){
    	Iterator<TXN>  itr=txntable.iterator();
    	while(itr.hasNext()){
    		TXN temp=itr.next();
    		if(temp.TXNID==txnID){
    			return temp;
    		}	
    	}
    	return null;
    }
    
    private void sendResponseMessage(){
    	String str=method + " " + txn_id + " " + seq_num + " \r\n\r\n\r\n";
    	sendToClient(str);
    	return ;
    }
    
    private void sendToClient(String response){	
		// to send response to client
    	byte[] toSend =(response).getBytes();	; //send bytes to client
    	try  
		{
			System.out.println("=======================");
			System.out.println("MESSAGE RESPONSE TO Client:");
			System.out.println("====> begin message");
			System.out.println(response);
			System.out.println("====> end message");
			socketOut.write(toSend);
			
		}
		catch( IOException ioe)
		{
			System.out.println("Could not send message to the server: " + ioe.getMessage());
			return;
		}
		try{
		socketIn.close();
		socketOut.close();
		server.close();
		}catch( IOException ioe)
		{
			System.out.println("Could not send buffer: " + ioe.getMessage());
			return;
		}
		return ; 
    }
   
    //prepare for response message regarding different errors
    private String errorMessageReady(int errorCode){
    	String str="";    	
    	if(errorCode==201){
    		reason= "Invalid transaction ID.";	
    	}else if(errorCode==202){
    		reason= "Invalid operation. ";   		
    	}else if(errorCode==204){
    		reason= "Wrong message format."; 		
    	}else if(errorCode==205){
    		reason= "File I/O error";  	
    	}else if(errorCode==206){
    		reason= "File not found";   		   	
    	}else{
    		reason= "Unkown reasons";   
    	}
    	method="error";
		reasonContent_length=new Integer(reason.length()).toString();
		str= method + " " + txn_id + " " + seq_num + " " +new Integer(errorCode).toString()+" "+reasonContent_length
				+"\r\n\r\n"+reason;
    	return str ;
    }//end errorReport
  
    //to check whether the file path is correct.
    //it is wrong to try to pass a directory or create a hidden files
    private boolean isFilePathCorrect(){
    	if(data.contains("/")){
    		return false;
    	}else if(data.startsWith(".")){
    		return false;
    	}else if(data.length()==0){
    		return false;
    	}else{
    		return true;
    	}   		
    }
    
    private boolean isMessageHeadFormatCorrect(){
    	/* Check if the method is valid */
		if(!method.equals("WRITE") &&
				!method.equals("NEW_TXN") &&
				!method.equals("COMMIT") &&
				!method.equals("ABORT") &&
				!method.equals("READ"))
		{	return false;
		}else if(new Integer(seq_num).intValue()<0)	{
			return false;
		}else if(new Integer(content_length).intValue()<0){
			return false;
		}else{
			return true;
		}
    }
     
}//end class


